"use strict";

/**
	This constructor is used to create a gui object.
	@constructor
*/
var gui = {};

/**
	list of section elements that represent tabs.
 */
gui.tabs = new Array();

/**
	stores the information abour curretn visible bubbles (type 1 or type 2 or no bubble).
 */
gui.bubbleInfo = 0;

/**
	stores the index of the current selected and shwon alignment path.
 */
gui.path = 0;

/**
	timeout before the compare algoritm starts.
 */
gui.compareTimer = false;

/**
	timeout before the alignment output starts.
 */
gui.alignmentTimer = false;

/**
	timeout to restart the sequence comparing.
 */
gui.compareAgain = false;

/**
	stores the state, of algorithm processing.
 */
gui.inProgress = true;

/**
	stores the last used sequence A.
 */
gui.lastSeqA = '';

/**
	stores the last used sequence B.
 */
gui.lastSeqB = '';

/**
	stores the curretn used sequence A.
 */
gui.seqA = '';

/**
	stores the curretn used sequence B.
 */
gui.seqB = '';

/**
	stores the sequence A of example output.
 */
gui.exampleSeqA = '-GAT';

/**
	stores the sequence B of example output.
 */
gui.exampleSeqB = '-GGA';

/**
	stores the matrix data of example output.
 */
gui.exampleData = [[{"horizontal":false,"vertical":false,"diagonal":false,"partOfPath":[0,1,2],"value":0},{"horizontal":false,"vertical":true,"diagonal":false,"partOfPath":[1],"value":1},{"horizontal":false,"vertical":true,"diagonal":false,"partOfPath":[],"value":2},{"horizontal":false,"vertical":true,"diagonal":false,"partOfPath":[],"value":3}],[{"horizontal":true,"vertical":false,"diagonal":false,"partOfPath":[],"value":1},{"horizontal":false,"vertical":false,"diagonal":true,"partOfPath":[0,2],"value":0},{"horizontal":false,"vertical":true,"diagonal":true,"partOfPath":[1,2],"value":1},{"horizontal":false,"vertical":true,"diagonal":false,"partOfPath":[],"value":2}],[{"horizontal":true,"vertical":false,"diagonal":false,"partOfPath":[],"value":2},{"horizontal":true,"vertical":false,"diagonal":false,"partOfPath":[],"value":1},{"horizontal":false,"vertical":false,"diagonal":true,"partOfPath":[0],"value":1},{"horizontal":false,"vertical":false,"diagonal":true,"partOfPath":[1,2],"value":1}],[{"horizontal":true,"vertical":false,"diagonal":false,"partOfPath":[],"value":3},{"horizontal":true,"vertical":false,"diagonal":false,"partOfPath":[],"value":2},{"horizontal":true,"vertical":false,"diagonal":true,"partOfPath":[],"value":2},{"horizontal":true,"vertical":false,"diagonal":true,"partOfPath":[0,1,2],"value":2}]];

/**
	collect informations about the HTML content and show the inital state.
 */
gui.init = function() {
	var sections = document.getElementsByTagName('section');
	for(var i in sections) {
		var section = sections[i];
		if (section.className == 'tab') {
			this.tabs.push(section);
		}
	}
	this.switchTab(this.tabs[0].id);
	this.showResult();
	this.inProgress = false
	$('modeControl').disabled = true;
	this.showInputInfo();
	$('progressblocking').style.display = 'none';
}

/**
	switch to the tab with the given id.
	
	@param {number} id - tab id
 */
gui.switchTab = function(id) {
	for(var i in this.tabs) {
		var tab = this.tabs[i];
		if(tab.id == id) {
			tab.style.display = 'block';
			$('tabline').getElementsByTagName('li')[i].className = 'current';
		} else {
			tab.style.display = 'none';
			$('tabline').getElementsByTagName('li')[i].className = 'none';
		}
	}
};

/**
	starts the comparing of the current stored sequences.
 */
gui.compareSequences = function() {
	this.lastSeqB = this.seqB;
	this.lastSeqA = this.seqA;
	if(this.seqA != '' && this.seqB != '') {
		algorithm.seqA = this.seqA;
		algorithm.seqB = this.seqB;
		algorithm.calculateArray(function() {
			algorithm.calculateMatrixPath();
			gui.showResult(algorithm.seqA, algorithm.seqB, algorithm.array);
			gui.clearAlignments();
			if(gui.alignmentTimer) {
				window.clearTimeout(gui.alignmentTimer);
				gui.alignmentTimer = false;
			}
			gui.alignmentTimer = window.setTimeout(function() {
				gui.showAlignments();
				gui.alignmentTimer = false;
			}, 1000);
			gui.inProgress = false;
			if(gui.compareAgain) {
				gui.ontypeCompareSequences(gui.compareAgain);
			}
		});
	} else {
		this.showResult();
		if(gui.alignmentTimer) {
			window.clearTimeout(gui.alignmentTimer);
			gui.alignmentTimer = false;
		}
		this.inProgress = false;
		this.compareAgain = false;
	}
};

/**
	called by changing events of sequence input fields to check the inputs and starts the timeout for the edit distance calculation.
	
	@param {DOMobject} obj - input element with changed value
 */
gui.ontypeCompareSequences = function(obj) {
	if(this.compareTimer) {
		window.clearTimeout(this.compareTimer);
		this.compareTimer = false;
		this.inProgress = false;
	}
	if(obj.id == 'inputSeqB') {
		var sB = obj.value.toUpperCase();
		if(sB == '') {
			$('inputSeqBvalidation').style.visibility = 'hidden';
			this.compareAgain = false;
			this.showResult();
			this.lastSeqB = '';
			return;
		} else {
			$('inputSeqBvalidation').style.visibility = 'visible';
			if(!this.validateInput(sB)) {
				$('inputSeqBvalidation').src = 'images/wrong.png';
				return;
			} else {
				$('inputSeqBvalidation').src = 'images/right.png';
			}
		}
		if(!this.inProgress && sB != this.lastSeqB) {
			this.inProgress = true;
			var sA = $('inputSeqA').value.toUpperCase();
			this.seqA = sA;
			this.seqB = sB;
			this.compareTimer = window.setTimeout(function() {
				gui.compareSequences();
				gui.compareTimer = false;
			}, 100);
			this.compareAgain = false;
		} else if(this.inProgress) {
			this.compareAgain = obj;
		}
	} else {
		var sA = obj.value.toUpperCase();
		if(sA == '') {
			$('inputSeqAvalidation').style.visibility = 'hidden';
			this.compareAgain = false;
			this.showResult();
			this.lastSeqA = '';
			return;
		} else {
			$('inputSeqAvalidation').style.visibility = 'visible';
			if(!this.validateInput(sA)) {
				$('inputSeqAvalidation').src = 'images/wrong.png';
				return;
			} else {
				$('inputSeqAvalidation').src = 'images/right.png';
			}
		}
		if(!this.inProgress && sA != this.lastSeqA) {
			this.inProgress = true;
			var sB = $('inputSeqB').value.toUpperCase();
			this.seqA = sA;
			this.seqB = sB;
			this.compareTimer = window.setTimeout(function() {
				gui.compareSequences();
				gui.compareTimer = false;
			}, 100);
			this.compareAgain = false;
		} else if(this.inProgress) {
			this.compareAgain = obj;
		}
	}
};

/**
	output the matrix table with the given results and marks the path with the given index.
	
	@param {string} seqA - first sequence
	@param {string} seqB - second sequence
	@param {object} data - 2d-array with the calulated matrix
	@param {number} pathNo - alingment index.
 */
gui.showResult = function(seqA, seqB, data, pathNo) {
	if(typeof(seqA) == 'undefined') {
		seqA = this.exampleSeqA;
		seqB = this.exampleSeqB;
		data = this.exampleData;
		$('modeControl').disabled = true;
		$('exampleinfo').style.display = '';
		gui.clearAlignments();
	} else {
		$('modeControl').disabled = false;
		$('exampleinfo').style.display = 'none';
	}
	if(typeof(pathNo) == 'undefined') {
		pathNo = 0;
	}
	$('topheadline').setAttribute('colspan', seqA.length);
	$('leftheadline').setAttribute('rowspan', seqB.length);
	var rows = $('result').getElementsByTagName('tr');
	while(rows[1].hasChildNodes()) {
		rows[1].removeChild(rows[1].firstChild);
	}
	for(var i = 0; i < seqA.length; i++) {
		var th = document.createElement('th');
		th.className = 'top';
		th.appendChild(document.createTextNode(seqA.charAt(i)));
		rows[1].appendChild(th);
	}
	while(rows[2].childNodes.length > 2) {
		rows[2].removeChild(rows[2].lastChild);
	}
	var th = document.createElement('th');
	th.className = 'left';
	th.appendChild(document.createTextNode(seqB.charAt(0)));
	rows[2].appendChild(th);
	for(var i = 0; i < seqA.length; i++) {
		rows[2].appendChild(this.createResultCell(data, i, 0, pathNo));
	}
	while($('result').getElementsByTagName('tbody')[0].childNodes.length > 5) {
		$('result').getElementsByTagName('tbody')[0].removeChild($('result').getElementsByTagName('tbody')[0].lastChild);
	}
	for(var j = 1; j < seqB.length; j++) {
		var tr = document.createElement('tr');
		var th = document.createElement('th');
		th.className = 'left';
		th.appendChild(document.createTextNode(seqB.charAt(j)));
		tr.appendChild(th);
		for(var i = 0; i < seqA.length; i++) {
			tr.appendChild(this.createResultCell(data, i, j, pathNo));
		}
		$('result').getElementsByTagName('tbody')[0].appendChild(tr);
	}
};

/**
	creates one cell of the result table.
	
	@param {object} data - 2d-array of the whole matrix
	@param {number} x - x-index on the data-array
	@param {number} y - y-index on the data-array
	@param {number} pathNo - alingment index.
 */
gui.createResultCell = function(data, x, y, pathNo) {
	var td = document.createElement('td');
	var c = document.createElement('div');
	var dollar = data[x][y].value=="$";
	var isPartOfPath = this.cellIsPartOfPath(data[x][y], pathNo);
	if(isPartOfPath) {
		td.className = 'path';
	}
	if(dollar){
		var input = document.createElement('input');
		input.id='user_input';
		input.type='text';
		c.appendChild(input);
	} else {
		c.appendChild(document.createTextNode(data[x][y].value+""));
	}
	if(data[x][y].horizontal) {
		var div = document.createElement('div');
		if(isPartOfPath && x>0 && this.cellIsPartOfPath(data[x-1][y], pathNo)) {
			div.className = 'a_left_path';
		} else {
			div.className = 'a_left';
		}
		c.appendChild(div);
	}
	if(data[x][y].vertical) {
		var div = document.createElement('div');
		if(isPartOfPath && y>0 && this.cellIsPartOfPath(data[x][y-1], pathNo)) {
			div.className = 'a_top_path';
		} else {
			div.className = 'a_top';
		}
		c.appendChild(div);
	}
	if(data[x][y].diagonal) {
		var div = document.createElement('div');
		if(isPartOfPath && x>0 && y>0 && this.cellIsPartOfPath(data[x-1][y-1], pathNo)) {
			div.className = 'a_topleft_path';
		} else {
			div.className = 'a_topleft';
		}
		c.appendChild(div);
	}
	td.appendChild(c);
	if(dollar){
		window.setTimeout(function() {
			$('user_input').focus();
		}, 10);
	}
	return td;
}

/**
	returns the DOM-object reference of one cell of the result table.
 
	@param {number} row - row index of the result cells, starting with 0.
	@param {number} col - colum index of the result cells, starting with 0.
 */
gui.getResultCell = function(row, col) {
	var row = $('result').getElementsByTagName('tr')[row+2];
	return row.getElementsByTagName('td')[col];
};

/**
	remove the alignment list from the DOM.
 */
gui.clearAlignments = function() {
	$('alignmentinfo').style.display = 'none';
	$('alignments').style.display = 'none';
	while($('alignments').hasChildNodes()) {
		$('alignments').removeChild($('alignments').firstChild);
	}
};

/**
	generate the alignment list from the data that are currently stored in the static algorithm-class.
 */
gui.showAlignments = function() {
	$('progressblocking').style.display = 'block';
	this.clearAlignments();
	window.setTimeout(function() {
		var count = algorithm.array[algorithm.array.length-1][algorithm.array[0].length-1].partOfPath.length;
		for(var i = 0; i<count; i++) {
			var table = document.createElement('table');
			table.setAttribute('onclick', '$("alignSel_' + i + '").checked=true; gui.selectAlignment(' + i + ');');
			table.style.cursor = 'pointer';
			var alignment = algorithm.getAlignment(i);
			for(var j=0; j<2; j++) {
				var tr = document.createElement('tr');
				if(j==0) {
					var th = document.createElement('th');
					th.setAttribute('rowspan', 2);
					var input = document.createElement('input');
					if(i==0) {
						input.checked = true;
					}
					input.type = 'radio'
					input.name = 'alignSel';
					input.id = 'alignSel_' + i;
					input.setAttribute('onchange', 'if(this.checked) gui.selectAlignment(' + i + ');');
					th.appendChild(input);
					tr.appendChild(th);
				}
				for(var k=0; k<alignment[j].length; k++) {
					var td = document.createElement('td');
					td.appendChild(document.createTextNode(alignment[j][k]));
					if(alignment[0][k] != alignment[1][k]) {
						td.className = 'change';
						if(alignment[0][k] == '-') {
							td.className += ' insert';
						} else if(alignment[1][k] == '-') {
							td.className += ' delete';
						} else {
							td.className += ' update';
						}
					}
					tr.appendChild(td);
				}
				table.appendChild(tr);
			}
			$('alignments').appendChild(table);
		}
		$('alignmentinfo').style.display = 'block';
		$('alignments').style.display = 'block';
		$('progressblocking').style.display = 'none';
	}, 1);
};

/**
	called by event, when the user changed the alignment selection.
 
	@param {number} idx - index of selected alignment
 */
gui.selectAlignment = function(idx) {
	this.path = idx;
	if(guistep.stepByStepMode) {
		guistep.stepCall();
	} else {
		this.showResult(algorithm.seqA, algorithm.seqB, algorithm.array, idx);
	}
};

/**
	generates bubbles for the current focused input field.
 
	@param {DOMobject} field - input field
 */
gui.showInputInfo = function(field) {
	if(typeof(field) == 'undefined') {
		if(!$('modeControl').disabled) {
			this.showBubble('modeControl', 'Switch to the <b>Step-by-step</b> mode to get the comparison calculation step by step', 60, 250);
		} else {
			this.showBubble('inputSet', 'Input two sequences', 27, 150);
		}
	} else {
		this.showBubble(field, 'Input your sequence here! Valid inputs:<br><b>nucleic acid sequences:</b><br>A, C, G, T <img src="images/question.png" onMouseover="gui.showBubbleExtension(\'A = Adenine<br>C = Cytosine<br>G = Guanine<br>T = Thymine\', 75)" onMouseout="gui.hideBubbleExtension()"><br><b>amino acid sequences:</b><br>A, C, D, E, F, G, H, I, K, L, M, N, P, Q, R, S, T, V, W, Y <img src="images/question.png" onMouseover="gui.showBubbleExtension(\'A = Alanine<br>C = Cysteine<br>D = Aspartic acid<br>E = Glutamic acid<br>F = Phenylalanine<br>G = Glycine<br>H = Histidine<br>I = Isoleucine<br>K = Lysine<br>L = Leucine<br>M = Methionine<br>N = Asparagine<br>P = Proline<br>Q = Glutamine<br>R = Arginine<br>S = Serine<br>T = Threonine<br>V = Valine<br>W = Tryptophan<br>Y = Tyrosine\', 330)" onMouseout="gui.hideBubbleExtension()">', 95, 390);
	}
};

/**
	returns TRUE if the given matrix-cell is part of the given alignment path.
 
	@param {cellInfo} data - one cell of the result matrix.
	@param {number} pathNo - index of the alignment path
 */
gui.cellIsPartOfPath = function(data, pathNo) {
	if(data.partOfPath.length>0) {
		for(var x in data.partOfPath) {
			if(data.partOfPath[x] == pathNo) {
				return true;
			}
		}
	}
	return false;
}

/**
	validates the given string to a valid nucleid or amino acid sequence.
 
	@param {string} seq - sequence to check
 */
gui.validateInput = function(seq) {
	var regExp = new RegExp("^[ARNDCEQGHILKMFPSTWYV]+$");
	var seqCorrect = regExp.test(seq);
	if(debug)console.log("seqCorrect: " + seqCorrect);
	return seqCorrect;
};

/**
	enable or diable the bubbles. called by event on the "activate info" checkbox.
 */
gui.toggleBubble = function() {
	if($('infoControl').checked && this.bubbleInfo == 1) {
		$('bubble').style.display = 'block';
		$('bubble2').style.display = 'none';
	} else if($('infoControl').checked && this.bubbleInfo == 2) {
		$('bubble2').style.display = 'block';
		$('bubble').style.display = 'none';
	} else {
		$('bubble').style.display = 'none';
		$('bubble2').style.display = 'none';
	}
}

/**
	called by event, when the user clicks on any visible bubble. The bubble will be closed.
 */
gui.clickBubble = function() {
	this.hideBubble();
};

/**
	show the bubble with the given content (string or DOM-object) linked to the given exists DOM-object (or ID) obj and optional with the given inner width (px) and height (px).

	@param {DOMobject} obj - DOM object or an id of existing DOM object.
	@param {string} content - HTML formatted string or DOM-object as bubble content.
	@param {number} height - bubble height as inner pixels
	@param {number} width - bubble width as inner pixels
	@param {boolean} toBottom - Henable the bubble type 2.
 */
gui.showBubble = function(obj, content, height, width, toBottom) {
	if(typeof(content) == 'string') {
		var p = document.createElement('p');
		p.innerHTML = content;
		content = p;
	}
	if(typeof(height) == 'undefined') {
		height = 150;
	}
	if(typeof(width) == 'undefined') {
		width = 150;
	}
	if(typeof(toBottom) == 'undefined') {
		toBottom = false;
	}
	var objPos = this.getPosition(obj);
	if(toBottom) {
		$('bubble2').style.width = (width + 55) + 'px';
		$('bubble2').style.height = (height + 80) + 'px';
		$('bubble2').style.left = Math.round(objPos.left + objPos.width - 17) + 'px';
		$('bubble2').style.top = Math.round(objPos.top + objPos.height/2 - 10) + 'px';
		while($('bubble2_content').hasChildNodes()) {
			$('bubble2_content').removeChild($('bubble2_content').firstChild);
		}
		$('bubble2_content').appendChild(content);
		this.bubbleInfo = 2;
	} else {
		$('bubble').style.width = (width + 55) + 'px';
		$('bubble').style.height = (height + 80) + 'px';
		$('bubble').style.left = Math.round(objPos.left + objPos.width - 17) + 'px';
		$('bubble').style.top = Math.round(objPos.top + objPos.height/2 - height - 70) + 'px';
		while($('bubble_content').hasChildNodes()) {
			$('bubble_content').removeChild($('bubble_content').firstChild);
		}
		$('bubble_content').appendChild(content);
		this.bubbleInfo = 1;
	}
	this.hideBubbleExtension();
	this.toggleBubble();
};

/**
	show the bubble extension on the bottom of the current bubble with the given content (string or DOM-object) and optional with the given inner height (px).
	
	@param {string} content - HTML formatted string or DOM-object as bubble extension content.
	@param {number} height - Bubble extension height as inner pixels
 */
gui.showBubbleExtension = function(content, height) {
	if(typeof(content) == 'string') {
		var p = document.createElement('p');
		p.innerHTML = content;
		content = p;
	}
	if(typeof(height) == 'undefined') {
		height = 100;
	}
	if(this.bubbleInfo == 1) {
		while($('bubbleExt_content').hasChildNodes()) {
			$('bubbleExt_content').removeChild($('bubbleExt_content').firstChild);
		}
		$('bubbleExt_content').appendChild(content);
		$('bubbleExt').style.top = (parseInt($('bubble').style.height)-60) + 'px';
		$('bubbleExt').style.height = (height + 25) + 'px';
		$('bubbleExt').style.display = 'block';
	} else {
		while($('bubbleExt2_content').hasChildNodes()) {
			$('bubbleExt2_content').removeChild($('bubbleExt2_content').firstChild);
		}
		$('bubbleExt2_content').appendChild(content);
		$('bubbleExt2').style.top = (parseInt($('bubble2').style.height)-20) + 'px';
		$('bubbleExt2').style.height = (height + 25) + 'px';
		$('bubbleExt2').style.display = 'block';
	}
}

/**
	Hide the current bubble extension.
 */
gui.hideBubbleExtension = function() {
	$('bubbleExt').style.display = 'none';
	$('bubbleExt2').style.display = 'none';
}

/**
	Hide the current bubble.
 */
gui.hideBubble = function() {
	this.bubbleInfo = 0;
	this.toggleBubble();
};

/**
	recursive function to get the position offset of the given DOM object.
	
	@param {DOMobject} content - DOM object to check the current position offset
	@param {object} offset - object with x and y offset
 */
gui.getOffset = function(object, offset) {
    if (!object) {
		return;
	}
	offset.x += object.offsetLeft;
	offset.y += object.offsetTop;
	this.getOffset(object.offsetParent, offset);
};

/**
	recursive function to get the scrolled position of the given DOM object.
	
	@param {DOMobject} content - DOM object to check the current scrolled position
	@param {object} scrolled - object with x and y scroll position
 */
gui.getScrolled = function(object, scrolled) {
	if (!object) {
		return;
	}
	scrolled.x += object.scrollLeft;
	scrolled.y += object.scrollTop;
	if (object.tagName.toLowerCase () != "html") {
		this.getScrolled(object.parentNode, scrolled);
	}
};

/**
	calculates the DOM object position and size, screen or page ralative
	
	@param {DOMobject} element - DOM object to check the current position offset
	@param {boolean} screen - true to get the screen relative position, else: get the page absolute position.
 */
gui.getPosition = function(element, screen) {
	if(typeof(element) == 'string') {
		element = $(element);
	}
	if(typeof(screen) == 'undefined') {
		screen = false;
	}
	var offset = {x : 0, y : 0};
	this.getOffset(element, offset);
	var scrolled = {x : 0, y : 0};
	if(screen) {
		this.getScrolled(element.parentNode, scrolled);
	}
	return {left: offset.x - scrolled.x, top: offset.y - scrolled.y, width: element.offsetWidth, height: element.offsetHeight};
};

window.addEventListener('load', function() {
	gui.init();
});